package com.flca.frw.components
{
	import flash.events.EventDispatcher;
	import flash.display.DisplayObject;
	import flash.events.MouseEvent;
	import mx.controls.Alert;
	import mx.core.Container;
	import mx.core.UIComponent;
	import mx.events.CloseEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.events.FaultEvent;
	
	import com.flca.frw.events.EventSaveComplete;
	import com.flca.frw.events.EventReadyForClose;
	import com.flca.frw.events.EventStatusUpdate;
	import com.flca.frw.interfaces.Controller;
	import com.flca.frw.util.ValidationUtil;
	
	[Event(name=EventSaveComplete.SAVE_COMPLETE,    type="framework.events.EventSaveComplete")]
	[Event(name=EventReadyForClose.READY_FOR_CLOSE, type="framework.events.EventReadyForClose")]
	[Event(name=EventOpenNewView.OPEN_NEW_VIEW,     type="framework.events.EventOpenNewView")]
	[Event(name=EventStatusUpdate.STATUS_UPDATE,    type="framework.events.EventStatusUpdate")]
	public class BaseCtrlr extends EventDispatcher implements Controller
	{
		protected var readOnly:Boolean = true;
		
		protected var btnClose:UIComponent;
		protected var view:Container;
		
		protected var initialHash:int;
		protected var createMode:Boolean = false;
		protected var closeAfterSave:Boolean = false;
		protected var hasModel:Boolean = true;
		
		protected var validationOk:Boolean = true;
		
		public var persistMode:Boolean = true;
		
		public function BaseCtrlr()
		{
			// normally, there is nothing to do here
			// add code in subclasses when necessary
		}
		
		public function getDisplayName():String
		{
			// OVERRIDE this method
			
			return "";
		}
		
		public function getClassName():String
		{
			// OVERRIDE this method
			
			return "";
		}
		
		public function getCurrentMdl():Object
		{
			// OVERRIDE this method
			
			// This method is typically used to obtain a
			// reference to the active model 
			
			return null;
		}
		
		protected function setCurrentMdl(aModel:Object):void
		{
			// OVERRIDE this method
			
			// This method is typically used to store a
			// reference to the model for binding purposes
			
			return;
		}
		
		public function getInitialHash():int
		{
			return initialHash;
		}
		
		public function init(aModel:Object, aInitialHash:int = 0):void
		{
			// DO NOT OVERRIDE this method ... use initComplete()
			
			if (hasModel)
			{
				if (aModel != null)
				{
					createMode = false;
					setCurrentMdl(aModel);
				}
				else
				{
					createMode = true;
					setCurrentMdl(createMdl());
				}
				
				updateViewFromModel();
				
				if (aInitialHash == 0)
				{
					initialHash = hashMdl();
				}
				else
				{
					initialHash = aInitialHash;
				}
			}
			
			initComplete();
			manageGuiState();
		}
		
		protected function initComplete():void
		{
			// OVERRIDE this method if desired
			
			return;
		}
		
		protected function createMdl():Object
		{
			// OVERRIDE this method
			
			// This method is typically used to create
			// an empty model for in the use case of
			// creation of a new entity
			
			return new Object();
		}
		
		protected function parseMdl(aMdl:Object):Object
		{
			// OVERRIDE this method
			
			// This method is typically used to create
			// a copy of the current model. This copy
			// is to be returned to the framework when
			// the user decides to cancel made changes
			return new Object();
		}
		
		protected function hashMdl():int
		{
			// OVERRIDE this method
			
			// This method is typically used to calculate
			// the hashcode of the current model in order
			// to perform dirty checks upon close
			return 0;
		}
		
		protected function updateViewFromModel():void
		{
			// OVERRIDE this method
			
			// This method is typically used to copy the
			// model values to the UI controls
			
			return;
		}
		
		public function updateModelFromView():void
		{
			// OVERRIDE this method
			
			// This method is typically used to copy the
			// UI values to the model
			
			return;
		}
		
		public function close():void
		{
			btnCloseClicked(null);
		}
		
		public function getView():DisplayObject
		{
			// OVERRIDE this method
			
			// Perform lazy instantiation of the view
			return null;
		}
		
		protected function btnCloseClicked(aEvent:MouseEvent):void
		{
			var hash:int;
			
			if (hasModel)
			{
				updateModelFromView();
				hash = hashMdl();
			}
			
			if (hasModel && !readOnly && hash != initialHash)
			{
				// there are unsaved changes!
				Alert.show("There are unsaved changes. Would you like to save now?", "Warning!", Alert.YES | Alert.NO | Alert.CANCEL, null, handleAlertOutcome, null, Alert.YES);
			}
			else
			{
				dispatchEvent(new EventReadyForClose(getCurrentMdl()));
			}
		}
		
		private function handleAlertOutcome(aEvent:CloseEvent):void
		{
			if (aEvent.detail == Alert.YES)
			{
				// listen for save-complete event, 
				// since view needs to be closed
				addEventListener(EventSaveComplete.SAVE_COMPLETE, handleSaveCompleteOnClose);
				
				performSave();
			}
			else if (aEvent.detail == Alert.CANCEL)
			{
				// do nothing. Stay in the window
			}
			else
			{
				dispatchEvent(new EventReadyForClose(getCurrentMdl()));
			}
		}
		
		private function handleSaveCompleteOnClose(aEvent:EventSaveComplete):void
		{
			dispatchEvent(new EventReadyForClose(getCurrentMdl()));
		}
		
		protected function performSave():void
		{
			sendStatusUpdate("saving ...");
			
			var hash:int;
			var msg:String;
			
			if (hasModel)
			{
				updateModelFromView();
				hash = hashMdl();
			}
			
			if (!hasModel || hash != initialHash)
			{
				validationOk = true;
				msg = validateView();
				if (msg == null)
				{
					persistModel(handleWsResult, handleWsFault);
				}
				else
				{
					validationOk = false;
					sendStatusUpdate("validation failed");
					Alert.show(msg, "Application Feedback");
				}
			}
			else
			{
				sendStatusUpdate("nothing to save");
				Alert.show("No changes made. There is nothing to save.", "Application Feedback");
			}
		}
		
		protected function validateView():String
		{
			// OVERRIDE this method only if additional validation
			// is required. Don't forget to call this method from
			// the overriden method!!!
			// If all validations are passed 'null' should be returned,
			// otherwise a compelling message about the validation error
			
			var result:String;
			
			if (ValidationUtil.validateAll(getValidators()))
			{
				result = null;
			}
			else
			{
				result = "Not all validations are passed.";
			}
			
			return result;
			
		}
			
		protected function getValidators():Array
		{
			// OVERRIDE this method
			
			// Normally this method returns all
			// relevant validators of the view
			
			return new Array();
		}
		
		protected function persistModel(aResultCallback:Function, aFaultCallBack:Function):void
		{
			// OVERRIDE this method if not readonly controller
			
			// Normally this method locates a backend
			// object (typically a web service),
			// and dependent on the business key, it will
			// either call a create or update operation
			// make sure that the passed callback functions are
			// registered correctly on the backend object
			
			// dependent on the createMode flag, the implementing
			// method should either call a create or update operation
			// in the backend service. After a successful create
			// operation, the createMode flag will be updated to
			// 'false'.
			
			aResultCallback();
		}
		
		protected function handleWsResult(aResult:ResultEvent):void
		{
			sendStatusUpdate("save ok");
			
			var resultMdl:Object;
			
			if (hasModel)
			{
				resultMdl = parseMdl(aResult.result);
				init(resultMdl);
			}
			else
			{
				resultMdl = null;
			}
			
			dispatchEvent(new EventSaveComplete(resultMdl));
			
			if (closeAfterSave)
			{
				dispatchEvent(new EventReadyForClose(getCurrentMdl()));
			}
		}
		
		protected function handleWsFault(aFault:FaultEvent):void
		{
			sendStatusUpdate("save failed");
			
			// remove listener for save-complete event, 
			// if it was possibly registered before
			removeEventListener(EventSaveComplete.SAVE_COMPLETE, handleSaveCompleteOnClose);
		}
		
	    public function createChildren():void 
	    {
	        btnClose = new CloseButton();
	        btnClose.addEventListener(MouseEvent.CLICK, btnCloseClicked);
			view.rawChildren.addChild(btnClose);
	    } 
		
		public function updateDisplayList(aUnscaledWidth:Number, aUnscaledHeight:Number):void 
		{ 
			btnClose.setActualSize(16, 16);
			btnClose.move(aUnscaledWidth - btnClose.width - 3, 3); 
		}
		
		protected function manageGuiState():void
		{
			// OVERRIDE this method
			
			// This method is typically used to enable/
			// disable/visualize GUI controls dependent
			// on the model context
			
			return;
		}
		
		protected function sendStatusUpdate(aMessage:String):void
		{
			dispatchEvent(new EventStatusUpdate(aMessage));
		}
		
		public function getCallbackReadyForClose():Function
		{
			// OVERRIDE this method
			//
			// only if this controller is interested in the close
			// event of a possible child controller
			
			return null;
		}
		
		public function getCallbackSaveComplete():Function
		{
			// OVERRIDE this method
			//
			// only if this controller is interested in the save complete
			// event of a possible child controller
			
			return null;
		}
		
		public function handleDeepLink():void
		{
			// OVERRIDE this method
			//
			// only if deeplinking for this controller is applicable
			
			return;
		}
	}
}